﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using EasyLib.Configuration;
using EasyLib.Extensions;
using EasyLib.Utils;

namespace EasyLib.Configuration
{
    /// <summary>
    ///  Confguration utils to load configuration data to cache from files (INI, resx, properties) and provides method to get configuration data. 
    /// </summary>
    public class ConfigUtils
    {
        private const string DATABASE_CONFIG_SECTION_KEY = "databaseConfigs";

        private const string FILE_CONFIG_SECTION_KEY = "fileConfigs";

        private const string DB_CONFIG_CONNECTION_STR_KEY = "DBConfigConnection";

        private static readonly object lockObj = new object();

        private static IDictionary<string, IDictionary<string, string>> _cache;

        public static string DEFAULT_ARRAY_SEPARATOR = ",";

        public static IDictionary<string, IDictionary<string, string>> Cache
        {
            get
            {
                return _cache;
            }
            internal set
            {
                _cache = value;
            }
        }

        /// <summary>
        /// Get configuration item value by key
        /// </summary>
        /// <param name="key">Key of configuration item</param>
        /// <returns>Value of configuration item</returns>
        public static string GetConfig(string key)
        {
            return GetConfig<string>(key);
        }

        /// <summary>
        /// Get configuration item value by key using generic approach
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">Key of configuration item</param>
        /// <returns>Value of configuration item with type T.</returns>
        public static T GetConfig<T>(string key)
        {
            if (Cache == null || Cache.Count ==0)
            {
                LoadAll();
            }
            foreach (Dictionary<string, string> dict in Cache.Values)
            {
                if (dict.ContainsKey(key))
                {
                    string s = dict[key];
                    return TypeConvertUtils.ConvertType<T>(s);
                }
            }
            return default(T);
        }

        /// <summary>
        /// Get Config Array.
        /// <example>
        /// int[] intArr = GetConfigArray<int>("myConfig", ",");
        /// </example>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">config key</param>
        /// <param name="separator">split separator</param>
        /// <returns>Config array</returns>
        public static T[] GetConfigArray<T>(string key, string separator)
        {
            string val = GetConfig<string>(key);
            if (val == null)
            {
                return null;
            }
            string[] eles = val.Split(new[] { separator }, StringSplitOptions.None);
            T[] destArr = new T[eles.Length];
            for (int i = 0; i < eles.Length; i++)
            {
                string e = typeof(T) == typeof(string) ? eles[i] : (eles[i].IsNullOrBlank() ? null : eles[i].Trim());
                destArr[i] = TypeConvertUtils.ConvertType<T>(e);
            }
            return destArr;
        }

        /// <summary>
        /// Get Config array which split by comma(,).
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">config key</param>
        /// <returns>Config array</returns>
        public static T[] GetConfigArray<T>(string key)
        {
            return GetConfigArray<T>(key, DEFAULT_ARRAY_SEPARATOR);
        }

        /// <summary>
        /// Get configuration item value by key and category
        /// </summary>
        /// <param name="key">Key of configuration item</param>
        /// <param name="category">Category of configuration items</param>
        /// <returns>Value of configuration item</returns>
        public static string GetConfig(string key, string category)
        {
            return GetConfig<string>(key, category);
        }

        /// <summary>
        /// Get configuration item value by key and category using generic approach
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">Key of configuration item</param>
        /// <param name="category">Category of configuration item.</param>
        /// <returns>Value of configuration item with type T.</returns>
        public static T GetConfig<T>(string key, string category)
        {
            if (Cache == null || Cache.Count == 0)
            {
                LoadAll();
            }
            IDictionary<string, string> keyValueMap = GetConfigByCategory(category);
            if (keyValueMap != null && keyValueMap.ContainsKey(key))
            {
                string s = keyValueMap[key];
                return TypeConvertUtils.ConvertType<T>(s);
            }
            return default(T);
        }

        /// <summary>
        /// Get Config array by key and category.
        /// <example>
        /// int[] intArr = GetConfigArray<int>("key","category",",");
        /// </example>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">config key</param>
        /// <param name="category">config category</param>
        /// <param name="separator">split separator</param>
        /// <returns></returns>
        public static T[] GetConfigArray<T>(string key, string category, string separator)
        {
            string value = GetConfig<string>(key, category);
            if (value == null)
            {
                return null;
            }
            string[] eles = value.Split(new[] { separator }, StringSplitOptions.None);
            T[] destArr = new T[eles.Length];
            for (int i = 0; i < eles.Length; i++)
            {
                string e = typeof(T) == typeof(string) ? eles[i] : (eles[i].IsNullOrBlank() ? null : eles[i].Trim());
                destArr[i] = TypeConvertUtils.ConvertType<T>(e);
            }
            return destArr;

        }

        /// <summary>
        /// Get key and value map by category of configuration items
        /// </summary>
        /// <param name="category">Category of configuration items</param>
        /// <returns>Key and value map</returns>
        public static IDictionary<string, string> GetConfigByCategory(string category)
        {
            if (Cache == null || Cache.Count == 0)
            {
                LoadAll();
            }
            return (Cache.ContainsKey(category)) ? Cache[category] : null;
        }

        /// <summary>
        /// Refresh all configuration items
        /// </summary>
        public static void Refresh()
        {
            Cache = null;
            LoadAll();
        }

        #region Private methods

        /// <summary>
        /// load all configuration items, including from files and database tables
        /// </summary>
        private static void LoadAll()
        {
            lock (lockObj)
            {
                if (Cache != null && Cache.Count != 0)
                {
                    return;
                }
                Cache = new Dictionary<string, IDictionary<string, string>>();
                LoadFileConfigs();
                LoadDatabaseConfigs();
            }
        }

        /// <summary>
        /// Load all database configuration items based on configuration in web.config/app.config
        /// </summary>
        private static void LoadDatabaseConfigs()
        {
            DatabaseConfigSection section = ConfigurationManager.GetSection(DATABASE_CONFIG_SECTION_KEY) as DatabaseConfigSection;
            if (section == null) return;
            DatabaseConfigElementCollection dcol = section.DatabaseConfig;
            if (dcol.Count == 0) return;
            foreach (object o in dcol)
            {
                string connectionString;
                DatabaseConfigElement element = o as DatabaseConfigElement;
                if (element.Name.IsNullOrBlank())
                {
                    throw new DatabaseConfigurationException("Table name is null or empty");
                }
                string tableName = element.Name.Trim();
                if (!element.ConnectionStringName.IsNullOrBlank())
                {
                    string connectionStringName = element.ConnectionStringName.Trim();
                    connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
                }
                else
                {
                    connectionString = CommonUtils.GetAppSettingValue(DB_CONFIG_CONNECTION_STR_KEY);
                }
                if (connectionString.IsNullOrBlank())
                {
                    throw new DatabaseConfigurationException("Connection String is empty");
                }
                DatabaseConfig config = new DatabaseConfig(tableName, connectionString.Trim());
                config.LoadData();
            }
        }

        /// <summary>
        /// Load all file configuration items based on configuration in web.config/app.config
        /// </summary>
        private static void LoadFileConfigs()
        {
            FileConfigSection section = ConfigurationManager.GetSection(FILE_CONFIG_SECTION_KEY) as FileConfigSection;
            if (section == null) return;
            FileConfigElementCollection fcol = section.Files;
            if (fcol.Count == 0) return;
            foreach (object o in fcol)
            {
                FileConfigElement element = o as FileConfigElement;
                FileConfig config = GetFileConfig(element.File);
                config.LoadData();
            }
        }

        /// <summary>
        /// Get file config object according to the extension of file path.
        /// </summary>
        /// <param name="filePath">full path of the file</param>
        /// <returns>file config object, may be IniConfig, ResxConfig, PropertiesConfig</returns>
        private static FileConfig GetFileConfig(string filePath)
        {
            string realPath = CommonUtils.GetRealPath(filePath);
            //File not exists
            if (realPath.IsNullOrBlank() || !File.Exists(realPath))
            {
                throw new ArgumentException("file not exists");
            }
            filePath = filePath.Trim();
            
            if (IsResxFile(filePath))
            {
                return new ResxConfig(filePath);
            }
            if (IsPropertiesFile(filePath))
            {
                return new PropertiesConfig(filePath);
            }
            throw new FileConfigurationException("file type unsupported");
        }

        private static bool IsResxFile(string filePath)
        {
            return Path.GetExtension(filePath).Equals(".resx", StringComparison.OrdinalIgnoreCase);
        }

        private static bool IsPropertiesFile(string filePath)
        {
            return Path.GetExtension(filePath).Equals(".properties", StringComparison.OrdinalIgnoreCase);
        }

        #endregion

        /// <summary>
        ///  It is used for get category from web.config/app.config for resx, properties files
        /// </summary>
        /// <param name="filePath">file path</param>
        /// <returns>category based on the configuration</returns>
        internal protected static string GetCategoryIfResxOrPropsFile(string filePath)
        {
            FileConfigSection section = ConfigurationManager.GetSection("fileConfigs") as FileConfigSection;
            FileConfigElementCollection fcol = section.Files;

            if (fcol[filePath].Category.IsNullOrBlank())
            {
                return "DEFAULT_CATEGORY";
            }
            return fcol[filePath].Category.Trim();
        }
    }
}